
#include "PyObjectBase.h"


typedef struct {
    PyObject_HEAD
        PyObject* baseobject;
    PyObject* weakreflist;  /* List of weak references */
} PyBaseProxy;

// Constructor
PyObjectBase::PyObjectBase(void* p, PyTypeObject* T)
    : _pcTwinPointer(p)
    , baseProxy(nullptr)
    , attrDict(nullptr)
{
    Py_TYPE(this) = T;
    _Py_NewReference(this);
    StatusBits.set(Valid);
    StatusBits.set(Notify);
}

PyObjectBase::~PyObjectBase()
{
    if (baseProxy && reinterpret_cast<PyBaseProxy*>(baseProxy)->baseobject == this)
        Py_DECREF(baseProxy);
    Py_XDECREF(attrDict);
}

static void
PyBaseProxy_dealloc(PyObject* self)
{
    if (reinterpret_cast<PyBaseProxy*>(self)->weakreflist != nullptr)
        PyObject_ClearWeakRefs(self);
    Py_TYPE(self)->tp_free(self);
}

static PyTypeObject PyBaseProxyType = {
    PyVarObject_HEAD_INIT(nullptr, 0)
    "PyBaseProxy",                                          /*tp_name*/
    sizeof(PyBaseProxy),                                    /*tp_basicsize*/
    0,                                                      /*tp_itemsize*/
    PyBaseProxy_dealloc,                                    /*tp_dealloc*/
#if PY_VERSION_HEX >= 0x03080000
    0,                                                      /*tp_vectorcall_offset*/
#else
    nullptr,                                                /*tp_print*/
#endif
    nullptr,                                                /*tp_getattr*/
    nullptr,                                                /*tp_setattr*/
    nullptr,                                                /*tp_compare*/
    nullptr,                                                /*tp_repr*/
    nullptr,                                                /*tp_as_number*/
    nullptr,                                                /*tp_as_sequence*/
    nullptr,                                                /*tp_as_mapping*/
    nullptr,                                                /*tp_hash*/
    nullptr,                                                /*tp_call */
    nullptr,                                                /*tp_str  */
    nullptr,                                                /*tp_getattro*/
    nullptr,                                                /*tp_setattro*/
    nullptr,                                                /*tp_as_buffer*/
    Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DEFAULT,               /*tp_flags */
    "Proxy class",                                          /*tp_doc */
    nullptr,                                                /*tp_traverse */
    nullptr,                                                /*tp_clear */
    nullptr,                                                /*tp_richcompare */
    offsetof(PyBaseProxy, weakreflist),                     /*tp_weaklistoffset */
    nullptr,                                                /*tp_iter */
    nullptr,                                                /*tp_iternext */
    nullptr,                                                /*tp_methods */
    nullptr,                                                /*tp_members */
    nullptr,                                                /*tp_getset */
    nullptr,                                                /*tp_base */
    nullptr,                                                /*tp_dict */
    nullptr,                                                /*tp_descr_get */
    nullptr,                                                /*tp_descr_set */
    0,                                                      /*tp_dictoffset */
    nullptr,                                                /*tp_init */
    nullptr,                                                /*tp_alloc */
    nullptr,                                                /*tp_new */
    nullptr,                                                /*tp_free   Low-level free-memory routine */
    nullptr,                                                /*tp_is_gc  For PyObject_IS_GC */
    nullptr,                                                /*tp_bases */
    nullptr,                                                /*tp_mro    method resolution order */
    nullptr,                                                /*tp_cache */
    nullptr,                                                /*tp_subclasses */
    nullptr,                                                /*tp_weaklist */
    nullptr,                                                /*tp_del */
    0,                                                      /*tp_version_tag */
    nullptr                                                 /*tp_finalize */
#if PY_VERSION_HEX >= 0x03090000
    ,0                                                      /*tp_vectorcall */
#elif PY_VERSION_HEX >= 0x03080000
    ,0                                                      /*tp_vectorcall */
    /* bpo-37250: kept for backwards compatibility in CPython 3.8 only */
    ,0                                                      /*tp_print */
#endif
};

PyTypeObject PyObjectBase::Type = {
    PyVarObject_HEAD_INIT(&PyType_Type,0)
    "PyObjectBase",                                         /*tp_name*/
    sizeof(PyObjectBase),                                   /*tp_basicsize*/
    0,                                                      /*tp_itemsize*/
    /* --- methods ---------------------------------------------- */
    PyDestructor,                                           /*tp_dealloc*/
#if PY_VERSION_HEX >= 0x03080000
    0,                                                      /*tp_vectorcall_offset*/
#else
    nullptr,                                                /*tp_print*/
#endif
    nullptr,                                                /*tp_getattr*/
    nullptr,                                                /*tp_setattr*/
    nullptr,                                                /*tp_compare*/
    __repr,                                                 /*tp_repr*/
    nullptr,                                                /*tp_as_number*/
    nullptr,                                                /*tp_as_sequence*/
    nullptr,                                                /*tp_as_mapping*/
    nullptr,                                                /*tp_hash*/
    nullptr,                                                /*tp_call */
    nullptr,                                                /*tp_str  */
    __getattro,                                             /*tp_getattro*/
    __setattro,                                             /*tp_setattro*/
    /* --- Functions to access object as input/output buffer ---------*/
    nullptr,                                                /* tp_as_buffer */
    /* --- Flags to define presence of optional/expanded features */
    Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DEFAULT,                 /*tp_flags */
    "The most base class for Python binding",               /*tp_doc */
    nullptr,                                                /*tp_traverse */
    nullptr,                                                /*tp_clear */
    nullptr,                                                /*tp_richcompare */
    0,                                                      /*tp_weaklistoffset */
    nullptr,                                                /*tp_iter */
    nullptr,                                                /*tp_iternext */
    nullptr,                                                /*tp_methods */
    nullptr,                                                /*tp_members */
    nullptr,                                                /*tp_getset */
    nullptr,                                                /*tp_base */
    nullptr,                                                /*tp_dict */
    nullptr,                                                /*tp_descr_get */
    nullptr,                                                /*tp_descr_set */
    0,                                                      /*tp_dictoffset */
    nullptr,                                                /*tp_init */
    nullptr,                                                /*tp_alloc */
    nullptr,                                                /*tp_new */
    nullptr,                                                /*tp_free   Low-level free-memory routine */
    nullptr,                                                /*tp_is_gc  For PyObject_IS_GC */
    nullptr,                                                /*tp_bases */
    nullptr,                                                /*tp_mro    method resolution order */
    nullptr,                                                /*tp_cache */
    nullptr,                                                /*tp_subclasses */
    nullptr,                                                /*tp_weaklist */
    nullptr,                                                /*tp_del */
    0,                                                      /*tp_version_tag */
    nullptr                                                 /*tp_finalize */
#if PY_VERSION_HEX >= 0x03090000
    ,0                                                      /*tp_vectorcall */
#elif PY_VERSION_HEX >= 0x03080000
    ,0                                                      /*tp_vectorcall */
    /* bpo-37250: kept for backwards compatibility in CPython 3.8 only */
    ,0                                                      /*tp_print */
#endif
};

PyObject* createWeakRef(PyObjectBase* ptr)
{
    static bool init = false;
    if (!init) {
        init = true;
        if (PyType_Ready(&PyBaseProxyType) < 0)
            return nullptr;
    }

    PyObject* proxy = ptr->baseProxy;
    if (!proxy) {
        proxy = PyType_GenericAlloc(&PyBaseProxyType, 0);
        ptr->baseProxy = proxy;
        reinterpret_cast<PyBaseProxy*>(proxy)->baseobject = ptr;
    }

    PyObject* ref = PyWeakref_NewRef(proxy, nullptr);
    return ref;
}

PyObjectBase* getFromWeakRef(PyObject* ref)
{
    if (ref) {
        PyObject* proxy = PyWeakref_GetObject(ref);
        if (proxy && PyObject_TypeCheck(proxy, &PyBaseProxyType)) {
            return static_cast<PyObjectBase*>(reinterpret_cast<PyBaseProxy*>(proxy)->baseobject);
        }
    }

    return nullptr;
}

PyMethodDef PyObjectBase::Methods[] = {
    {nullptr, nullptr, 0, nullptr}        /* Sentinel */
};

PyObject* PyObjectBase::__getattro(PyObject* obj, PyObject* attro)
{
    const char* attr;
    attr = PyUnicode_AsUTF8(attro);

    if (streq(attr, "__class__")) {
        PyObject* res = PyObject_GenericGetAttr(obj, attro);
        if (res)
            return res;
    }

    PyObjectBase* pyObj = static_cast<PyObjectBase*>(obj);
    if (!pyObj->isValid()) {
        PyErr_Format(PyExc_ReferenceError, "Cannot access attribute '%s' of deleted object", attr);
        return nullptr;
    }

#ifdef ATTR_TRACKING
    // If an attribute references this as parent then reset it (bug #0002902)
    PyObject* cur = pyObj->getTrackedAttribute(attr);
    if (cur) {
        if (PyObject_TypeCheck(cur, &(PyObjectBase::Type))) {
            PyObjectBase* base = static_cast<PyObjectBase*>(cur);
            base->resetAttribute();
            pyObj->untrackAttribute(attr);
        }
    }
#endif

    PyObject* value = pyObj->_getattr(attr);
#ifdef ATTR_TRACKING
    if (value && PyObject_TypeCheck(value, &(PyObjectBase::Type))) {
        if (!static_cast<PyObjectBase*>(value)->isConst() &&
            !static_cast<PyObjectBase*>(value)->isNotTracking()) {
            static_cast<PyObjectBase*>(value)->setAttributeOf(attr, pyObj);
            pyObj->trackAttribute(attr, value);
        }
    }
    else
#endif
        if (value && PyCFunction_Check(value)) {
            PyCFunctionObject* cfunc = reinterpret_cast<PyCFunctionObject*>(value);
            if (!cfunc->m_self) {
                Py_DECREF(cfunc);
                value = nullptr;
                PyErr_Format(PyExc_AttributeError, "<no object bound to built-in method %s>", attr);
            }
        }

    return value;
}

int PyObjectBase::__setattro(PyObject* obj, PyObject* attro, PyObject* value)
{
    const char* attr;
    attr = PyUnicode_AsUTF8(attro);
    if (value == nullptr) {
        PyErr_Format(PyExc_AttributeError, "Cannot delete attribute: '%s'", attr);
        return -1;
    }
    else if (!static_cast<PyObjectBase*>(obj)->isValid()) {
        PyErr_Format(PyExc_ReferenceError, "Cannot access attribute '%s' of deleted object", attr);
        return -1;
    }

#ifdef ATTR_TRACKING
    PyObject* cur = static_cast<PyObjectBase*>(obj)->getTrackedAttribute(attr);
    if (cur) {
        if (PyObject_TypeCheck(cur, &(PyObjectBase::Type))) {
            PyObjectBase* base = static_cast<PyObjectBase*>(cur);
            base->resetAttribute();
            static_cast<PyObjectBase*>(obj)->untrackAttribute(attr);
        }
    }
#endif

    int ret = static_cast<PyObjectBase*>(obj)->_setattr(attr, value);
#ifdef ATTR_TRACKING
    if (ret == 0) {
        static_cast<PyObjectBase*>(obj)->startNotify();
    }
#endif
    return ret;
}

/*------------------------------
 * PyObjectBase attributes	-- attributes
------------------------------*/
PyObject* PyObjectBase::_getattr(const char* attr)
{
    if (streq(attr, "__class__")) {
        Py_INCREF(Py_TYPE(this));
        return (PyObject*)(Py_TYPE(this));
    }
    else if (streq(attr, "__members__")) {
        return nullptr;
    }
    else if (streq(attr, "__dict__")) {
        // Return the default dict
        PyTypeObject* tp = Py_TYPE(this);
        Py_XINCREF(tp->tp_dict);
        return tp->tp_dict;
    }
    else if (streq(attr, "softspace")) {
        // Internal Python stuff
        return nullptr;
    }
    else {
        // As fallback solution use Python's default method to get generic attributes
        PyObject* w, * res;
        w = PyUnicode_InternFromString(attr);
        if (w != nullptr) {
            res = PyObject_GenericGetAttr(this, w);
            Py_XDECREF(w);
            return res;
        }
        else {
            // Throw an exception for unknown attributes
            PyTypeObject* tp = Py_TYPE(this);
            PyErr_Format(PyExc_AttributeError, "%.50s instance has no attribute '%.400s'", tp->tp_name, attr);
            return nullptr;
        }
    }
}

int PyObjectBase::_setattr(const char* attr, PyObject* value)
{
    if (streq(attr, "softspace"))
        return -1; // filter out softspace
    PyObject* w;
    // As fallback solution use Python's default method to get generic attributes
    w = PyUnicode_InternFromString(attr); // new reference
    if (w != nullptr) {
        // call methods from tp_getset if defined
        int res = PyObject_GenericSetAttr(this, w, value);
        Py_DECREF(w);
        return res;
    }
    else {
        // Throw an exception for unknown attributes
        PyTypeObject* tp = Py_TYPE(this);
        PyErr_Format(PyExc_AttributeError, "%.50s instance has no attribute '%.400s'", tp->tp_name, attr);
        return -1;
    }
}

/*------------------------------
 * PyObjectBase repr    representations
------------------------------*/
PyObject* PyObjectBase::_repr()
{
    std::stringstream a;
    a << "<base object at " << _pcTwinPointer << ">";
# ifdef FCDebug
    Console().Log("PyObjectBase::_repr() not overwritten representation!");
# endif
    return Py_BuildValue("s", a.str().c_str());
}

// Tracking functions

void PyObjectBase::resetAttribute()
{
    if (attrDict) {
        // This is the attribute name to the parent structure
        // which we search for in the dict
        PyObject* key1 = PyBytes_FromString("__attribute_of_parent__");
        PyObject* key2 = PyBytes_FromString("__instance_of_parent__");
        PyObject* attr = PyDict_GetItem(attrDict, key1);
        PyObject* inst = PyDict_GetItem(attrDict, key2);
        if (attr) {
            PyDict_DelItem(attrDict, key1);
        }
        if (inst) {
            PyDict_DelItem(attrDict, key2);
        }
        Py_DECREF(key1);
        Py_DECREF(key2);
    }
}

void PyObjectBase::setAttributeOf(const char* attr, PyObject* par)
{
    if (!attrDict) {
        attrDict = PyDict_New();
    }

    PyObject* key1 = PyBytes_FromString("__attribute_of_parent__");
    PyObject* key2 = PyBytes_FromString("__instance_of_parent__");
    PyObject* attro = PyUnicode_FromString(attr);
    PyDict_SetItem(attrDict, key1, attro);
    PyDict_SetItem(attrDict, key2, par);
    Py_DECREF(attro);
    Py_DECREF(key1);
    Py_DECREF(key2);
}

void PyObjectBase::startNotify()
{
    if (!shouldNotify())
        return;

    if (attrDict) {
        // This is the attribute name to the parent structure
        // which we search for in the dict
        PyObject* key1 = PyBytes_FromString("__attribute_of_parent__");
        PyObject* key2 = PyBytes_FromString("__instance_of_parent__");
        PyObject* attr = PyDict_GetItem(attrDict, key1);
        PyObject* parent = PyDict_GetItem(attrDict, key2);
        if (attr && parent) {
            // Inside __setattr of the parent structure the 'attr'
            // is being removed from the dict and thus its reference
            // counter will be decremented. To avoid to be deleted we
            // must tmp. increment it and afterwards decrement it again.
            Py_INCREF(parent);
            Py_INCREF(attr);
            Py_INCREF(this);

            __setattro(parent, attr, this);

            Py_DECREF(parent); // might be destroyed now
            Py_DECREF(attr); // might be destroyed now
            Py_DECREF(this); // might be destroyed now

            if (PyErr_Occurred())
                PyErr_Clear();
        }
        Py_DECREF(key1);
        Py_DECREF(key2);
    }
}

PyObject* PyObjectBase::getTrackedAttribute(const char* attr)
{
    PyObject* obj = nullptr;
    if (attrDict) {
        obj = PyDict_GetItemString(attrDict, attr);
        obj = getFromWeakRef(obj);
    }
    return obj;
}

void PyObjectBase::trackAttribute(const char* attr, PyObject* obj)
{
    if (!attrDict) {
        attrDict = PyDict_New();
    }

    PyObject* obj_ref = createWeakRef(static_cast<PyObjectBase*>(obj));
    if (obj_ref) {
        PyDict_SetItemString(attrDict, attr, obj_ref);
    }
}

void PyObjectBase::untrackAttribute(const char* attr)
{
    if (attrDict) {
        PyDict_DelItemString(attrDict, attr);
    }
}

void PyObjectBase::clearAttributes()
{
    if (attrDict) {
        PyDict_Clear(attrDict);
    }
}
